import fs, { type MakeDirectoryOptions } from "fs";
import { join } from "path";
import util from "util";
import { Ipc } from "@main/helper/ipcMain";
import { Ipc_ChannelEnum } from "@enum/channel.enum";

interface WriteAndAppendFileParams<T> {
  path: string;
  data: T;
  options: {
    encoding?: BufferEncoding;
    mode?: number | string;
    flag?: string;
  };
}

type WriteFileParams = WriteAndAppendFileParams<string | Buffer>;
type AppendFileParams = WriteAndAppendFileParams<string | Uint8Array>;

export class FileService {
  /**
   * 删除目录
   */
  static deleteDirectory(path: string): void {
    let files: string[] = [];
    //判断路径是否存在
    if (fs.existsSync(path)) {
      //读取目录的内容
      files = fs.readdirSync(path);

      files.forEach((file, index) => {
        let curPath = join(path, file);

        //同步地获取文件或目录的状态信息  isDirectory 如果状态信息描述的是文件系统目录 则返回true
        if (fs.statSync(curPath).isDirectory()) {
          // 递归
          this.deleteDirectory(curPath);
        } else {
          //同步地删除文件或符号链接
          fs.unlinkSync(curPath);
        }
      });
      //同步删除目录 注意 fs.rmdir只能删除空目录 所以得先删除里面的文件
      fs.rmdirSync(path);
    }
  }

  /**
   * 检查文件是否存在于当前目录中、以及是否可写 0 不存在 1 只可读 2 存在可读写
   */
  static async access(path: string): Promise<0 | 1 | 2> {
    return new Promise((resolve) =>
      fs.access(path, fs.constants.F_OK, (err) => {
        if (err) err.code === "ENOENT" ? resolve(0) : resolve(1);
        else resolve(2);
      })
    );
  }

  /**
   * 删除文件
   */
  static async unlink(path: string): Promise<0 | 1> {
    return await this.promisifyFunc(fs.unlink, path);
  }

  /**
   * 文件重命名
   */
  static async rename(path: string, newPath: string): Promise<0 | 1> {
    return await this.promisifyFunc(fs.rename, path, newPath);
  }

  /**
   * 创建目录
   */
  static async mkdir(
    path: string,
    options: MakeDirectoryOptions
  ): Promise<0 | 1> {
    return await this.promisifyFunc(
      fs.rename,
      path,
      options || { recursive: true }
    );
  }

  /**
   * 创建文件
   */
  static async writeFile(params: WriteFileParams): Promise<0 | 1> {
    const { path, data, options } = params;

    return await this.promisifyFunc(fs.writeFile, path, data, options || {});
  }

  /**
   * 追加数据到文件
   */
  static async appendFile(params: AppendFileParams): Promise<0 | 1> {
    const { path, data, options } = params;

    return await this.promisifyFunc(fs.appendFile, path, data, options || {});
  }

  /**
   * 读取整个文件
   */
  static async readFile(
    path: string,
    options?: { encoding?: BufferEncoding; flag?: string }
  ): Promise<0 | string | Buffer> {
    return new Promise((resolve) =>
      fs.readFile(path, options, (err, data) => {
        if (err) resolve(0);
        resolve(data);
      })
    );
  }

  /**
   * 回调转异步统一处理
   */
  static async promisifyFunc<T>(fn: Function, ...args: any[]): Promise<0 | 1> {
    const promiseFn = util.promisify(fn.bind(fn, ...args));

    return new Promise((resolve) => {
      promiseFn()
        .then(() => {
          resolve(1);
        })
        .catch(() => {
          resolve(0);
        });
    });
  }

  static on(): void {
    Ipc.on<[string]>(Ipc_ChannelEnum.FS_DEL_DIR, (_, path) => {
      this.deleteDirectory(path);
    });
    Ipc.handle<[string]>(
      Ipc_ChannelEnum.FS_ACCESS,
      async (_, path) => await this.access(path)
    );

    Ipc.handle<[string]>(
      Ipc_ChannelEnum.FS_UNLINK,
      async (_, path) => await this.unlink(path)
    );

    Ipc.handle<[string, string]>(
      Ipc_ChannelEnum.FS_RENAME,
      async (_, path, newPath) => await this.rename(path, newPath)
    );

    Ipc.handle<[string, MakeDirectoryOptions]>(
      Ipc_ChannelEnum.FS_MKDIR,
      async (_, path, options) => await this.mkdir(path, options)
    );

    Ipc.handle<[WriteFileParams]>(
      Ipc_ChannelEnum.FS_WRITE_FILE,
      async (_, params) => await this.writeFile(params)
    );

    Ipc.handle<[AppendFileParams]>(
      Ipc_ChannelEnum.FS_APPEND_FILE,
      async (_, params) => await this.appendFile(params)
    );

    Ipc.handle<[string, { encoding?: BufferEncoding; flag?: string }?]>(
      Ipc_ChannelEnum.FS_READ_FILE,
      async (_, path, options) => await this.readFile(path, options)
    );
  }
}
